using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;

namespace Legend.Fakes.Tests
{
    [TestFixture]
    public class CallsToTests
    {
        [Test]
        public void CallsTo_does_not_match_call_where_arguments_does_not_match()
        {
            var foo = A.Fake<IFoo>();
            foo.Configure().CallsTo(x => x.Bar("test")).Returns("test");

            Assert.That(foo.Bar("something else"), Is.Not.EqualTo("test"));
        }

        [Test]
        public void CallsTo_matches_calls_where_arguments_matches()
        {
            var foo = A.Fake<IFoo>();
            foo.Configure().CallsTo(x => x.Bar("test")).Returns("test");

            Assert.That(foo.Bar("test"), Is.EqualTo("test"));
        }

        [TestCase("whatever")]
        [TestCase("w h a t e v er")]
        [TestCase("foo")]
        [TestCase("bar")]
        public void CallsTo_matches_anything_when_MatchAny_is_specified(string argument)
        {
            var foo = A.Fake<IFoo>();

            foo.Configure().CallsTo(x => x.Bar(Argument.Is.Any<string>())).Returns("test");

            Assert.That(foo.Bar(argument), Is.EqualTo("test"));
        }

        [Test]
        public void CallsTo_matches_when_predicate_matches_argument_value()
        {
            var foo = A.Fake<IFoo>();

            foo.Configure().CallsTo(x => x.Bar(Argument.Is.Match<string>(p => p == "test"))).Returns("test");

            Assert.That(foo.Bar("test"), Is.EqualTo("test"));
        }

        [Test]
        public void CallsTo_does_not_match_when_predicate_does_not_match_argument()
        {
            var foo = A.Fake<IFoo>();

            foo.Configure().CallsTo(x => x.Bar(Argument.Is.Match<string>(p => p == "something else"))).Returns("test");

            Assert.That(foo.Bar("test"), Is.Not.EqualTo("test"));
        }

        [Test]
        public void CallsTo_matches_when_arguments_matches_values_produced_by_method_expression()
        {
            var foo = A.Fake<IFoo>();

            foo.Configure().CallsTo(x => x.Bar("foo".ToString())).Returns("foo");
            
            Assert.That(foo.Bar("foo"), Is.EqualTo("foo"));
        }

        [Test]
        public void CallsTo_does_not_match_when_arguments_does_not_match_values_produced_by_method_expression()
        {
            var foo = A.Fake<IFoo>();

            foo.Configure().CallsTo(x => x.Bar("foo".ToString())).Returns("foo");

            Assert.That(foo.Bar("bar"), Is.Not.EqualTo("foo"));
        }

        [Test]
        public void CallsTo_matches_when_arguments_matches_values_produced_by_property_expression()
        {
            var foo = A.Fake<IFoo>();
            var propertyObject = new ClassWithStringProperty();
            propertyObject.Value = "foo";

            foo.Configure().CallsTo(x => x.Bar(propertyObject.Value)).Returns("foo");

            Assert.That(foo.Bar("foo"), Is.EqualTo("foo"));
        }

        [Test]
        public void CallsTo_does_not_match_when_arguments_does_not_match_values_produced_by_property_expression()
        {
            var foo = A.Fake<IFoo>();
            var propertyObject = new ClassWithStringProperty();
            propertyObject.Value = "bar";

            foo.Configure().CallsTo(x => x.Bar(propertyObject.Value)).Returns("foo");

            Assert.That(foo.Bar("foo"), Is.Not.EqualTo("foo"));
        }

        [Test]
        public void CallsTo_matches_when_arguments_matches_values_produced_by_field_expression()
        {
            var foo = A.Fake<IFoo>();
            string value = "foo";

            foo.Configure().CallsTo(x => x.Bar(value)).Returns("foo");

            Assert.That(foo.Bar("foo"), Is.EqualTo("foo"));
        }

        [Test]
        public void CallsTo_does_not_match_when_arguments_does_not_match_values_produced_by_field_expression()
        {
            var foo = A.Fake<IFoo>();
            string value = "bar";

            foo.Configure().CallsTo(x => x.Bar(value)).Returns("foo");

            Assert.That(foo.Bar("foo"), Is.Not.EqualTo("foo"));
        }

        [Test]
        public void CallsTo_matches_when_arguments_matches_values_produced_by_indexed_property_expression()
        {
            var foo = A.Fake<IFoo>();
            var propertyObject = new ClassWithStringProperty();

            foo.Configure().CallsTo(x => x.Bar(propertyObject["foo"])).Returns("foo");

            Assert.That(foo.Bar("foo"), Is.EqualTo("foo"));
        }

        [Test]
        public void CallsTo_does_not_match_when_arguments_does_not_match_values_produced_by_indexed_property_expression()
        {
            var foo = A.Fake<IFoo>();
            var propertyObject = new ClassWithStringProperty();

            foo.Configure().CallsTo(x => x.Bar(propertyObject["bar"])).Returns("foo");

            Assert.That(foo.Bar("foo"), Is.Not.EqualTo("foo"));
        }

        [Test]
        public void CallsTo_matches_first_time_only_when_subsequent_calls_has_been_specified_and_no_number_of_times_has_been_specified()
        {
            var foo = A.Fake<IFoo>();

            foo.Configure().CallsTo(x => x.Bar()).Returns("foo");
            foo.Configure().CallsTo(x => x.Bar()).Returns("bar");

            Assert.That(foo.Bar(), Is.EqualTo("foo"));
            Assert.That(foo.Bar(), Is.EqualTo("bar"));
        }

        [Test]
        public void CallsTo_matches_forever_if_no_subsequent_call_and_no_number_of_times_has_been_specified()
        {
            var foo = A.Fake<IFoo>();

            foo.Configure().CallsTo(x => x.Bar()).Returns("foo");

            for (int i = 0; i < 10; i++)
            {
                Assert.That(foo.Bar(), Is.EqualTo("foo"));
            }
        }

        [TestCase("anything", 10, true)]
        [TestCase("a n y t h i n g", 10, true)]
        [TestCase("anything", 9, false)]
        [TestCase("anything", 1, false)]
        public void CallsTo_can_mix_specific_values_and_Match_expressions(string stringValue, int intValue, bool shouldMatch)
        {
            var foo = A.Fake<IFoo>();

            foo.Configure().CallsTo(x => x.Bar(Argument.Is.Any<string>(), 10)).Returns("foo");

            var returnValue = foo.Bar(stringValue, intValue);
            Assert.That(returnValue == "foo", Is.EqualTo(shouldMatch));
        }

        [TestCase(1)]
        [TestCase(2)]
        [TestCase(3)]
        [TestCase(5)]
        [TestCase(8)]
        public void CallsTo_matches_only_the_specified_number_of_times(int numberOfTimes)
        {
            var foo = A.Fake<IFoo>();

            foo.Configure().CallsTo(x => x.Bar()).Returns("foo").NumberOfTimes(numberOfTimes);

            for (int i = 0; i < numberOfTimes; i++)
            {
                Assert.That(foo.Bar(), Is.EqualTo("foo"));
            }

            Assert.That(foo.Bar(), Is.Not.EqualTo("foo"));
        }

        [Test]
        public void CallsTo_matches_specified_number_of_times_before_using_next_specified_call()
        {
            var foo = A.Fake<IFoo>();

            foo.Configure().CallsTo(x => x.Bar()).Returns("foo").NumberOfTimes(2);
            foo.Configure().CallsTo(x => x.Bar()).Returns("bar");

            Assert.That(foo.Bar(), Is.EqualTo("foo"));
            Assert.That(foo.Bar(), Is.EqualTo("foo"));
            Assert.That(foo.Bar(), Is.EqualTo("bar"));
        }

        public class ClassWithStringProperty
        {
            public string Value { get; set; }

            public string this[string index]
            {
                get
                {
                    return index;
                }
            }
        }

        public interface IFoo
        {
            string Bar();
            string Bar(string value);
            string Bar(string s, int i);
            void Baz();

            int Count { get; set; }
        }
    }
}
